import qualified Data.Set as Set

data Direction = E
               | SE
               | SW
               | W
               | NW
               | NE
                 deriving (Show, Eq)

type Path = [Direction]
type Position = (Int, Int)

type State = Set.Set Position

parseDirection :: String -> (String, Direction)
parseDirection (s:ss) 
    | s == 'e' = (ss, E)
    | s == 'w' = (ss, W)
    | s == 's' = case head ss of
        'e' -> (tail ss, SE)
        'w' -> (tail ss, SW)
    | s == 'n' = case head ss of
        'e' -> (tail ss, NE)
        'w' -> (tail ss, NW)

parsePath :: String -> Path
parsePath str = 
    if null rest
        then [dir]
        else dir : parsePath rest
    where
        (rest, dir) = parseDirection str

parseInput :: String -> [Path]
parseInput = map parsePath . lines

{-
         [-1, 1] [ 0, 1]
    [-1, 0] [ 0, 0] [ 1, 0] 
         [ 0,-1] [ 1,-1]
-}

east :: Position -> Position
east (x, y) = (x+1, y)

west :: Position -> Position
west (x, y) = (x-1, y)

southeast :: Position -> Position
southeast (x, y) = (x+1, y-1)

southwest :: Position -> Position
southwest (x, y) = (x, y-1)

northeast :: Position -> Position
northeast (x, y) = (x, y+1)

northwest :: Position -> Position
northwest (x, y) = (x-1, y+1)

neighbors :: Position -> [Position]
-- neighbors pos = map (step pos) [E, W, SE, SW, NE, NW]
neighbors pos = [northwest pos, northeast pos,
                 west pos,      east pos,
                 southwest pos, southeast pos]

step :: Position -> Direction -> Position
step pos dir = case dir of
    E  -> east pos
    W  -> west pos
    SE -> southeast pos
    SW -> southwest pos
    NE -> northeast pos
    NW -> northwest pos

identify :: Path -> Position
identify = foldl step (0,0)

flipTile :: State -> Position -> State
flipTile state pos = 
    if Set.member pos state
        then Set.delete pos state
        else Set.insert pos state

dayUpdate :: State -> State
dayUpdate state = state''
    where
        blacks = Set.toList state

        updateBlack :: (State, State) -> Position -> (State, State)
        updateBlack (newBlacks, todoWhite) pos = 
            if blackcount == 0 || blackcount > 2 
                then (newBlacks, todoWhite')
                else (Set.insert pos newBlacks, todoWhite')
            where
                (blackcount, todoWhite') = foldl foldFunc (0, todoWhite) $ neighbors pos

                foldFunc :: (Int, State) -> Position -> (Int, State)
                foldFunc (blackcount, todoWhite) pos = 
                    if isBlack pos
                        then (blackcount+1, todoWhite)
                        else (blackcount,   Set.insert pos todoWhite)

        (state', whites) = foldl updateBlack (Set.empty, Set.empty) blacks

        state'' = foldl updateWhite state' (Set.toList whites)

        isBlack :: Position -> Bool
        isBlack pos = Set.member pos state 

        updateWhite :: State -> Position -> State
        updateWhite state pos = 
            if (length . filter isBlack . neighbors $ pos) == 2
                then Set.insert pos state
                else state

main :: IO ()
main = do
    input <- readFile "input.txt"
    let state = foldl flipTile Set.empty 
               . map identify
               . parseInput $ input

    print . length
          . last 
          . take 101
          . iterate dayUpdate
          $ state